package ctxSemanticsGen;

import java.lang.annotation.Annotation;

import java.lang.reflect.Method;



import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import cop.publ.LayeredClass;


/**
 * This class represents the structure of a class for which a COP semantics will
 * be generated.
 * 
 * @author Guido Salvaneschi
 *
 */
public class ClassStructure {
	
	private static final Logger logger = Logger.getLogger(ClassStructure.class);
	static{ logger.setLevel(Level.OFF); }

	// TODO: hardcoded in LayeredClass, change this
	public static final String UNASSIGNED_MAP = "[unassigned]";
	
	
	private Class<LayeredClass> myClass;	
	private ArrayList<ArrayList<Method>> str;
	private ArrayList<String[]> layerMapper;	

	
	/**
	 * Creates a new ClassStructure instance describing the class c. This method
	 * creates the data structures representing the partial method definitions
	 * declared in this class.
	 * 
	 * @param c
	 */
	public ClassStructure(Class c){
		myClass = c;
		/* The order is mandatory */
		layerMapper = initAnnotationTable();
		str = initClassLayersStructure();
	}
	
	
	/**
	 * Returns the class described by this ClassStructure instance.
	 * @return
	 */
	public Class getDescribedClass(){
		return myClass;
	}
	
	
	/*
	 * Initialize the structure which represent the information in the 
	 * LayeredClass annotation.
	 */
	private ArrayList<String[]> initAnnotationTable(){
		Annotation an = myClass.getAnnotation(LayeredClass.class);
		String[] val = ((LayeredClass)an).value();

		/* Initialize the map. At least will remain empty */
		ArrayList<String[]> mapper = new ArrayList<String[]>();
		
		/* The class does not declare any special mapping */
		if (val[0].equals(UNASSIGNED_MAP)){
			return mapper;
		}
		
		/* The class declares a mapping in the LayeredClass annotation */
		for (String s: val){ /* TODO: check that the string is well-formed */
			String[] a = s.split("=>");
			a[0] = a[0].trim();
			a[1] = a[1].trim();
			mapper.add(a);
		}
		return mapper;
	}
	
	
	/*
	 * Creates a list of lists containing all the advised and inLayer methods.
	 * Advised are the first methods in each sublist. Other methods in the sublist
	 * are the inLayer associated to the advised.
	 */
	private ArrayList<ArrayList<Method>> initClassLayersStructure(){
		
		ArrayList<ArrayList<Method>> classLayersStr = new ArrayList<ArrayList<Method>>();
		
		// All the methods of the class: advised + inLayer + normal
		Method[] ms = myClass.getDeclaredMethods();
		
		for (Method m: ms){
			
			boolean isInLayerMethod = (layerMapper.isEmpty()) ? 
					Utility.isInLayerMethodNoMap(m) : 
					Utility.isInLayerMethod(m,layerMapper);
			
			if(!isInLayerMethod){ /* not inLayer method */
				
				// Methods equal to m declared in different layers
				ArrayList<Method> ml = new ArrayList<Method>();
				
				for (Method classMth: ms){
					if( Utility.isBaseMethodOfInLayerMethod(m, classMth) ){
						ml.add(classMth);
					}
				}
				
				if(!ml.isEmpty()){  // Advised AND , keep in data str
					ml.add(0,m);
					classLayersStr.add(ml);				
				}
			}
		}
		return classLayersStr;
	}

	
	/**
	 * Returns all the methods in this class for which a layered version is
	 * present in the same class.
	 * @return
	 */
	public Set<Method> getAdvicedMethods(){
		
		Set<Method> s = new HashSet<Method>();
		for(ArrayList<Method> l : str){			
			s.add(l.get(0));
		}
		return s;
	}
	
	
	/**
	 * Returns all partial definitions of the given base method.
	 * @param m
	 * @return
	 */
	public Set<Method> getLayeredMethodsForAdvicedMethod(Method m){
		
		Set<Method> s = new HashSet<Method>();
		
		for(ArrayList<Method> l : str){			
			if( m.equals(l.get(0))){
				for(Method mthInLayer : l.subList(1,l.size())){
					logger.debug("adding: " + mthInLayer);
					s.add(mthInLayer);
				}
			}
		}
		return s;
	}
	
	
	
	
	/**
	 * Gives the partial definition in "layer" of the given base method 
	 * baseMethod.
	 * 
	 * @param baseMethod
	 * @param layer
	 * @return
	 *
	public Method getMethodForLayer(Method baseMethod, Class layer){
		for(ArrayList<Method> l : str){
			if(baseMethod.equals(l.get(0))){
				
				if(Utility.layerNameForLayerClass(layer).equals("Base")){
					return l.get(0);
				}
				
				for(Method mthInLayer : l.subList(1,l.size())){
					if(Utility.layerForName(mthInLayer.getName(),baseMethod.getName())
							.equals(Utility.layerNameForLayerClass(layer))){
						return mthInLayer;
					}
				}
			}
		}
		
		return null;
	}
	
	*/
	
	
	/**
	 * Returns all the partial method declarations in the given layer.
	 * @param layer
	 * @return
	 */
	public Set<Method> getMethodsDeclaredInLayer(Class<?> layer){
		
		Set<Method> s = new HashSet<Method>();
		
		/* Over base methods */
		for(ArrayList<Method> l : str){
			
			/* Over partial declarations of the base method */
			for(Method partialDecl : l.subList(1,l.size())){
				
				String inLayer = Utility.layerForName(partialDecl.getName(),
													  layerMapper);
				if( inLayer.equals(layer.getName())){ s.add(partialDecl); }
			}
		}
		return s;
	}
	
	
	/**
	 * Returns the adviced method (the base method) for the given in-layer 
	 * method.
	 * 
	 * @param inLayerMethod
	 * @return
	 */
	public Method getAdvicedMethodForInLayerMethod(Method inLayerMethod){
		
		for(ArrayList<Method> l : str){
			for(Method mthInLayer : l.subList(1,l.size())){
				if( mthInLayer.equals(inLayerMethod) ){ 
					return l.get(0); }
			}
		}
		throw new RuntimeException("No adviced method for inLayer method!");
	}
		
			
	public ArrayList<String[]> getLayerMapper(){
		return layerMapper;
	}

	
		
	
	public String toString(){
		String result = "";
		for (ArrayList<Method> ml : str){
			result += "\n---\n"; 
			for (Method m : ml){
				result += ("  " + m.toGenericString() + "\n");
			}
		}
		return result;
	}

	
}


